#include <stdlib.h>
#include <stdio.h>
#include <algorithm>
#include <cstdio>
#include <fstream>
#include <string>

#if defined(__APPLE__) && defined(__MACH__)
# include <GLUT/glut.h>
#else
#  include <windows.h>
#  include <GL/glut.h>

#endif

#include <iostream>
#include <math.h>

using namespace std;

int windowWidth = 800;
int windowHeight = 600;
char s[90];

float _AxisTheta[3] = { 0.0, 0.0, 0.0 };
float _SelfRotateTheta[3] = { 0.0, 0.0, 0.0 };
float _MeshTheta = 0.0;

float CtrlPoints[4][4][3] = { { { -2.0, -2.0, 0.0 }, { -1.0, -1.0, 0.0 }, {
		0.0, -1.0, 0.0 }, { 2.0, -1.0, 0.0 } }, // P00, P01, P02, P03
		{ { -2.0, 0.0, 0.0 }, { -1.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 }, { 1.0,
				0.0, 0.0 } }, // P10, P11, P12, P13
		{ { -3.0, 1.0, 0.0 }, { -1.0, 1.0, 0.0 }, { 0.0, 1.0, 0.0 }, { 2.0,
				1.0, 0.0 } }, // P20, P21, P22, P23
		{ { -3.0, 2.0, 0.0 }, { -1.0, 2.0, 0.0 }, { 0.0, 3.0, 0.0 }, { 1.0,
				2.8, 0.0 } } // P30, P31, P32, P33
};

int _axis = 1;
float PI = 3.1415926;

int mouseLocX, mouseLocY = 0;
float colorGreen[4] = { 0.0, 1.0, 0.0, 1.0 };
float colorYellow[4] = { 1.0, 1.0, 0.0, 1.0 };
float colorNone[4] = { 0.0, 0.0, 0.0, 1.0 };
float colorRed[4] = { 1.0, 0.0, 0.0, 1.0 };
int myframe, mytime, timebase = 0;
float fps = 0.0;
GLfloat diffuse0[] = { 0.2, 0.2, 0.2, 1.0 };
GLfloat ambient0[] = { 0.1, 0.1, 0.1, 1.0 };
GLfloat specular0[] = { 1.0, 0.0, 0.0, 1.0 };
GLfloat light0_pos[] = { 1.0, 2.0, 3, 0, 1.0 };

// This tells us if we want lines or fill mode
bool g_bRenderMode = true;
//this tells us if we want subdivision cycle or not
bool g_bsubdivisionMode = false;
int subdivisionMesh[5] = { 4, 7, 13, 25, 49 };
int subindex = 0;
int mainMenu;
int subdivisionMenu;
int shadeMenu;
ofstream myfile;

void drawMesh() {


//	glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3 * 4, 4, 0, 1, 3, 4, &CtrlPoints[0][0][0]);
	glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3 , 4, 0, 1, 3*4, 4, &CtrlPoints[0][0][0]);

	glMapGrid2f(subdivisionMesh[subindex], 0.0, 1.0, subdivisionMesh[subindex],
			0.0, 1.0);

	glEvalMesh2(GL_FILL, 0, subdivisionMesh[subindex], 0,
			subdivisionMesh[subindex]);

	//int i, j = 0;
	//    for (j = 0; j <= 8; j++)
	//    {
	//
	//        glBegin(GL_LINE_STRIP);
	//        for (i = 0; i <= 20; i++)
	//            glEvalCoord2f((GLfloat)i/20.0, (GLfloat)j/8.0);
	//        glEnd();
	//
	//        glBegin(GL_LINE_STRIP);
	//        for (i = 0; i <= 20; i++)
	//            glEvalCoord2f((GLfloat)j/8.0, (GLfloat)i/20.0);
	//        glEnd();
	//   }


	// //   glPointSize(4.0);
	////    glColor3f(1.0, 0.0, 0.0);
	//    glBegin(GL_POINTS);
	//    for(j=0; j<4 ; j++)
	//        for(i=0; i<4; i++)
	//        	glNormal3fv(&CtrlPoints[i][j][0]);
	//            glVertex3fv(&CtrlPoints[i][j][0]);

}

void init() {
	mainMenu = 1;
	subdivisionMenu = 2;
	shadeMenu = 3;

	glShadeModel(GL_SMOOTH);

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_NORMALIZE);

	//glEnable(GL_CULL_FACE);
	//glCullFace(GL_BACK);
	glEnable(GL_MAP2_VERTEX_3);
	glEnable(GL_AUTO_NORMAL);
	myfile.open ("fps.txt");

}

void display() {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	// Change the rendering mode to and from lines or triangles
	if (g_bRenderMode) {
		// Render the triangles in fill mode
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	} else {
		// Render the triangles in wire frame mode
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	}
	glTranslatef(0.0, 0.0, -10.0);

	glRotatef(_AxisTheta[0], 1.0, 0.0, 0.0);
	glRotatef(_AxisTheta[1], 0.0, 0.1, 0.0);

	glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, colorGreen);
	glScalef(1.7, 1.7, 1.7);

	drawMesh();

	myframe++;// used to calculate and display frame rate on window title.
	mytime = glutGet(GLUT_ELAPSED_TIME);
	if (mytime - timebase > 1500) {
		fps = myframe * 1000.0 / (mytime - timebase);
		sprintf(s, "Assignment 3 by Zhiyi Wu FPS:%4.2f", fps);

		myfile << fps<<endl;

		//this is used to cycle the subdivision mode
		if (g_bsubdivisionMode)
			subindex += 1;
		if (subindex > 4)
			subindex -= 5;
		glutSetWindowTitle(s);
		timebase = mytime;
		myframe = 0;
	}

	glutSwapBuffers();

}

void spinCube() {
	//	_AxisTheta[_axis] += 0.3;
	//	_SelfRotateTheta[_axis] += 1.1;
	int i, j, k = 0;

	int temp = 0;

	_MeshTheta += 5.1;

	//cout << sin(_MeshTheta * PI / 180) << endl;


	//for(k=0; k<3;k++){
	for (j = 0; j < 4; j++) {

		for (i = 0; i < 4; i++)

			CtrlPoints[j][i][2] = sin((_MeshTheta + temp) * PI / 180);
		temp += 90;
	}
	//}

	if (_MeshTheta > 360.0)
		_MeshTheta -= 360.0;
	if (_AxisTheta[_axis] > 360.0)
		_AxisTheta[_axis] -= 360.0;
	if (_SelfRotateTheta[_axis] > 360.0)
		_SelfRotateTheta[_axis] -= 360.0;

	glutPostRedisplay();
}

void mouse(int btn, int state, int x, int y) {
	mouseLocX = x;
	mouseLocY = y;

}

void mykey(unsigned char key, int x, int y) //used to clear the canvas
{
	if ((key == 'C') | (key == 'c')) {
		glutPostRedisplay();
	}
	if ((key == 'F') | (key == 'f')) {
		// We want to be able to turn wire frame on and off, so let's switch it
		g_bRenderMode = !g_bRenderMode;
	}
	if ((key == 'S') | (key == 's')) {
		// toggle to cycle the subdivision
		g_bsubdivisionMode = !g_bsubdivisionMode;
	}
	if ((key == 'Q') | (key == 'q')) {
			// quit and export the fps data to txt file
		myfile.close();
		exit(0);
		}
}

void motion(int x, int y) { //control the draw function when mouse button down and move


	if (mouseLocX != x) {
		_AxisTheta[1] += (x - mouseLocX);

	}
	if (mouseLocY != y) {
		_AxisTheta[0] += (y - mouseLocY);

	}
	mouseLocX = x;
	mouseLocY = y;

}

void AlternativePerspective(GLfloat fovy, GLfloat aspect, GLfloat zNear,
		GLfloat zFar) {
	GLfloat xmin, xmax, ymin, ymax;

	ymax = zNear * tan(fovy * PI / 360.0);
	ymin = -ymax;
	xmin = ymin * aspect;
	xmax = ymax * aspect;

	glFrustum(xmin, xmax, ymin, ymax, zNear, zFar);
}

void menu(int id) {

	switch (id) {
	case 3:
		glutPostRedisplay();
		break;
	case 4:
		myfile.close();
		exit(0);
		break;
	}
}

void subdivisionmenu(int id) {

	switch (id) {
	case 0:
		subindex = 0;
		break;
	case 1:
		subindex = 1;
		break;
	case 2:
		subindex = 2;

		break;
	case 3:
		subindex = 3;
		break;
	case 4:
		subindex = 4;
		break;

	}
}

void shademenu(int id) {
	switch (id) {
	case 0:

		break;
	case 1:

		break;

	}

}
void myReshape(int w, int h) {
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);//Applies subsequent matrix operations to the modelview matrix stack. this is used to contril the
	glLoadIdentity();//glLoadIdentity replaces the current matrix with the identity matrix.
	glOrtho(-2.0, 2.0, -2.0, 2.0, 2.0, -2.0);
	AlternativePerspective(50.0, (float) w / (float) h, 2.0, 10.0);

	glMatrixMode(GL_MODELVIEW);//Applies subsequent matrix operations to the projection matrix stack.

}

void outputfile(float fps){




}


int main(int argc, char** argv) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 600);
	glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH) - windowWidth) / 2,
			(glutGet(GLUT_SCREEN_HEIGHT) - windowHeight) / 2); // Set the windows centered in screen based on native resolution.

	glutCreateWindow("Assignment 3 by Zhiyi Wu");
	init();
	subdivisionMenu = glutCreateMenu(subdivisionmenu); //create subdivision menu
	glutAddMenuEntry("subdivision 0 4x4", 0);
	glutAddMenuEntry("subdivision 1 7x7", 1);
	glutAddMenuEntry("subdivision 2 13x13", 2);
	glutAddMenuEntry("subdivision 3 25x25", 3);
	glutAddMenuEntry("subdivision 4 49x49", 4);
	glutAttachMenu(GLUT_RIGHT_BUTTON);

	shadeMenu = glutCreateMenu(shademenu); //create shade menu
	glutAddMenuEntry("Small square", 0);
	glutAddMenuEntry("Medium square", 1);

	glutAttachMenu(GLUT_RIGHT_BUTTON);

	mainMenu = glutCreateMenu(menu);// create main menu
	glutAddSubMenu("subdivision", subdivisionMenu);
	glutAddSubMenu("Shade Mode", shadeMenu);
	glutAddMenuEntry("Clear Screen", 3);
	glutAddMenuEntry("Exit", 4);
	glutAttachMenu(GLUT_RIGHT_BUTTON);

	//	glEnableClientState( GL_COLOR_ARRAY); //this one cause the total damage.
//	glEnableClientState(GL_VERTEX_ARRAY);
//	glEnableClientState(GL_NORMAL_ARRAY);

	glutReshapeFunc(myReshape);
	glutDisplayFunc(display);
	glutIdleFunc(spinCube);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);
	glutKeyboardFunc(mykey);
	glEnable(GL_DEPTH_TEST);

	glutMainLoop();
	return 0;
}
